using System.Collections.Generic;
using OhioState.Graphics;
using OhioState.Graphics.Proxy;
using OhioState.Math;
using Tao.OpenGl;

namespace OhioState.Graphics.OpenGL
{
    /// <summary>
    /// This is a Singleton that provides OpenGL services, such as compiling and linking on the targetted OpenGL context.
    /// </summary>
    public sealed class GLShader
    {

        static GLShader()
        {
        }

        private GLShader()
        {
        }



        #region Set uniform variable methods
        // step 8: Once a valid program is created, parameters can be set for various types.
        //		   A variable name association is used rather than exposing the OpenGL handles.
        //
        // FLOAT Array
        //
        private static void SetVariableValue(uint programID, string variableName, float[] val)
        {
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform1fv(location, val.Length, val);
        }

        //
        // INT Array
        //
        private static void SetVariableValue(uint programID, string variableName, int[] val)
        {
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform1iv(location, val.Length, val);
        }

        //
        // BOOL Array
        //
        private static void SetVariableValue(uint programID, string variableName, bool[] val)
        {
                int[] array = new int[val.Length];
                for (int i = 0; i < array.Length; i++)
                {
                    if (val[i] == true)
                        array[i] = 1;
                    else
                        array[i] = 0;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform1iv(location, array.Length, array);
        }

        //
        // FLOAT_VEC2 array
        //
        private static void SetVariableValue(uint programID, string variableName, Vector2[] val)
        {
                int j = 0;
                int size = val.Length;
                float[] vec2 = new float[size*2];
                for (int i=0; i < size; i++)
                {
                    vec2[j] = val[i][0];
                    vec2[j + 1] = val[i][1];
                    j = j + 2;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform2fv(location, size*2, vec2);
        }

        //
        // FLOAT_VEC3 array
        //
        private static void SetVariableValue(uint programID, string variableName,Vector3[] val)
        {
                int j = 0;
                int size = val.Length;
                float[] vec3 = new float[size * 3];
                for (int i = 0; i < size; i++)
                {
                    vec3[j] = val[i][0];
                    vec3[j + 1] = val[i][1];
                    vec3[j + 2] = val[i][2];
                    j = j + 3;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform3fv(location, size*3, vec3);
        }

        //
        // FLOAT_VEC4 array
        //
        private static void SetVariableValue(uint programID, string variableName, Vector4[] val)
        {
                int j = 0;
                int size = val.Length;
                float[] vec4 = new float[size * 4];
                for (int i = 0; i < size; i++)
                {
                    vec4[j] = val[i][0];
                    vec4[j + 1] = val[i][1];
                    vec4[j + 2] = val[i][2];
                    vec4[j + 3] = val[i][3];
                    j = j + 4;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform4fv(location, size*4, vec4);
        }

        //
        // INT_VEC2 array
        //
        private static void SetVariableValue(uint programID, string variableName, Vector2i[] val)
        {
                int j = 0;
                int size = val.Length;
                int[] vec2i = new int[size * 2];
                for (int i = 0; i < size; i++)
                {
                    vec2i[j] = val[i][0];
                    vec2i[j + 1] = val[i][1];
                    j = j + 2;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform2iv(location, size*2, vec2i);
        }

        //
        // INT_VEC3 array
        //
        private static void SetVariableValue(uint programID, string variableName, Vector3i[] val)
        {
                int j = 0;
                int size = val.Length;
                int[] vec3i = new int[size * 3];
                for (int i = 0; i < size; i++)
                {
                    vec3i[j] = val[i][0];
                    vec3i[j + 1] = val[i][1];
                    vec3i[j + 2] = val[i][2];
                    j = j + 3;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform3iv(location, size*3, vec3i);
        }

        //
        // INT_VEC4 array
        //
        private static void SetVariableValue(uint programID, string variableName, Vector4i[] val)
        {
                int j = 0;
                int size = val.Length;
                int[] vec4i = new int[size * 4];
                for (int i = 0; i < size; i++)
                {
                    vec4i[j] = val[i][0];
                    vec4i[j + 1] = val[i][1];
                    vec4i[j + 2] = val[i][2];
                    vec4i[j + 3] = val[i][3];
                    j = j + 4;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform3iv(location, size*4, vec4i);
        }

        //
        // BOOL_VEC2 array
        //
        private static void SetVariableValue(uint programID, string variableName,Vector2b[] val)
        {
                int j = 0;
                int size = val.Length;
                int[] vec2b = new int[size*2];
                for (int i = 0; i < size; i++)
                {
                    if (val[i][0] == true)
                        vec2b[j] = 1;
                    else
                        vec2b[j] = 0;
                    if (val[i][1] == true)
                        vec2b[j + 1] = 1;
                    else
                        vec2b[j + 1] = 0;
                    j = j + 2;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform2iv(location, size*2, vec2b);
        }

        //
        // BOOL_VEC3 array
        //
        private static void SetVariableValue(uint programID, string variableName, Vector3b[] val)
        {
                int j = 0;
                int size = val.Length;
                int[] vec3b = new int[size * 3];
                for (int i = 0; i < size; i++)
                {
                    if (val[i][0] == true)
                        vec3b[j] = 1;
                    else
                        vec3b[j] = 0;
                    if (val[i][1] == true)
                        vec3b[j + 1] = 1;
                    else
                        vec3b[j + 1] = 0;
                    if (val[i][2] == true)
                        vec3b[j + 2] = 1;
                    else
                        vec3b[j + 2] = 0;
                    j = j + 3;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform3iv(location, size*3, vec3b);
        }

        //
        // BOOL_VEC4 array
        //
        private static void SetVariableValue(uint programID, string variableName, Vector4b[] val)
        {
                int j = 0;
                int size = val.Length;
                int[] vec4b = new int[size * 4];
                for (int i = 0; i < size; i++)
                {
                    if (val[i][0] == true)
                        vec4b[j] = 1;
                    else
                        vec4b[j] = 0;
                    if (val[i][1] == true)
                        vec4b[j + 1] = 1;
                    else
                        vec4b[j + 1] = 0;
                    if (val[i][2] == true)
                        vec4b[j + 2] = 1;
                    else
                        vec4b[j + 2] = 0;
                    if (val[i][3] == true)
                        vec4b[j + 3] = 1;
                    else
                        vec4b[j + 3] = 0;
                    j = j + 4;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform4iv(location, size*4, vec4b);
        }

        //
        // FLOAT_MAT2 array
        //
        private static void SetVariableValue(uint programID, string variableName, Matrix2[] val)
        {
                int j = 0;
                int size = val.Length;
                float[] mat2 = new float[size*4];
                for (int i=0; i < size; i++)
			    {
				    mat2[j] = val[i].getColVector(0)[0];
                    mat2[j + 1] = val[i].getColVector(0)[1];
                    mat2[j + 2] = val[i].getColVector(1)[0];
                    mat2[j + 3] = val[i].getColVector(1)[1];
                    j = j + 4;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniformMatrix2fv(location, size*4, Gl.GL_TRUE, mat2);
        }

        //
        // FLOAT_MAT3 array
        //
        private static void SetVariableValue(uint programID, string variableName, Matrix3[] val)
        {
                int j = 0;
                int size = val.Length;
                float[] mat3 = new float[size* 9];
                for (int i =0; i < size; i++)
			    {
				    mat3[j] = val[i].getColVector(0)[0];
                    mat3[j + 1] = val[i].getColVector(0)[1];
                    mat3[j + 2] = val[i].getColVector(0)[2];
				    mat3[j + 3] = val[i].getColVector(1)[0];
                    mat3[j + 4] = val[i].getColVector(1)[1];
                    mat3[j + 5] = val[i].getColVector(1)[2];
				    mat3[j + 6] = val[i].getColVector(2)[0];
                    mat3[j + 7] = val[i].getColVector(2)[1];
                    mat3[j + 8] = val[i].getColVector(2)[2];
                    j = j + 9;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniformMatrix3fv(location, size*9, Gl.GL_TRUE, mat3);
        }

        //
        // FLOAT_MAT4
        //
        private static void SetVariableValue(uint programID, string variableName, Matrix4[] val)
        {
                int size = val.Length;
                float[] mat4 = new float[size * 16];
                int index = 0;
                for (int i =0; i < size; i++)
			    {
                    float[] matrixValues = val[i].GetStreamColumnOrder();
                    for (int k = 0; k < 16; k++)
                    {
                        mat4[index++] = matrixValues[k];
                    }
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniformMatrix4fv(location, size*16, Gl.GL_TRUE, mat4);
        }

        //
        // SAMPLER array
        //
        private static void SetSamplerVariable(uint programID, string variableName, TextureProxy[] texture)
        {
                int size = texture.Length;
                int[] textureUnit = new int[size];
                for (int i = 0; i < size; i++)
                {
                    textureUnit[i] = textureCount++;
                }
                int location = Gl.glGetUniformLocation(programID, variableName);
                Gl.glUniform1iv(location, size, textureUnit);
        }
        //
        // Integer variable
        //
        private static void SetVariableValue(uint programID, string variableName, int val)
        {
            int location = Gl.glGetUniformLocation(programID, variableName);
            Gl.glUniform1f(location, (float)val);
        }
        #endregion Set uniform variable methods

        private static int textureCount = 0;
        private static void resetTextureUnits() { textureCount = 0; }

        //
        // Set uniform variables in the shader program
        //
        public static void SetUniformVariables(IRenderPanel panel, IOpenGLResource shader, Dictionary<string, UniformVariableValues> uvValues)
        {
            uint program = shader.GUID;
            int size = 0;
            //Enable(program);
            resetTextureUnits();
            foreach (string key in uvValues.Keys)
            {
                size = uvValues[key].Size;
                switch (uvValues[key].Type)
                {
                    case UniformVariableInfo.UniformType.FLOAT:
                        float[] floatVal = new float[size];
                        for (int i = 0; i < size; i++)
                        {
                            //changed by Oleg
                            //floatVal[i] = uvValues[key][i].Float;
                            floatVal[i] = uvValues[key][i].Float.Float_Value;
                        }
                        SetVariableValue(program, key, floatVal);
                        break;
                    case UniformVariableInfo.UniformType.FLOAT_VEC2:
                        Vector2[] vec2 = new Vector2[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec2[i] = (Vector2)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec2);
                        break;
                    case UniformVariableInfo.UniformType.INT:
                        int[] intVal = new int[size];
                        for (int i = 0; i < size; i++)
                        {
                            intVal[i] = uvValues[key][i].Int.Int_Value;
                        }
                        SetVariableValue(program, key, intVal);
                        break;
                    case UniformVariableInfo.UniformType.INT_VEC2:
                        Vector2i[] vec2i = new Vector2i[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec2i[i] = (Vector2i)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec2i);
                        break;
                    case UniformVariableInfo.UniformType.BOOL_VEC2:
                        Vector2b[] vec2b = new Vector2b[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec2b[i] = (Vector2b)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec2b);
                        break;
                    case UniformVariableInfo.UniformType.FLOAT_VEC3:
                        Vector3[] vec3 = new Vector3[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec3[i] = (Vector3)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec3);
                        break;
                    case UniformVariableInfo.UniformType.INT_VEC3:
                        Vector3i[] vec3i = new Vector3i[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec3i[i] = (Vector3i)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec3i);
                        break;
                    case UniformVariableInfo.UniformType.BOOL_VEC3:
                        Vector3b[] vec3b = new Vector3b[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec3b[i] = (Vector3b)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec3b);
                        break;
                    case UniformVariableInfo.UniformType.FLOAT_VEC4:
                        Vector4[] vec4 = new Vector4[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec4[i] = (Vector4)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec4);
                        break;
                    case UniformVariableInfo.UniformType.INT_VEC4:
                        Vector4i[] vec4i = new Vector4i[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec4i[i] = (Vector4i)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec4i);
                        break;
                    case UniformVariableInfo.UniformType.BOOL_VEC4:
                        Vector4b[] vec4b = new Vector4b[size];
                        for (int i = 0; i < size; i++)
                        {
                            vec4b[i] = (Vector4b)uvValues[key][i].Vec;
                        }
                        SetVariableValue(program, key, vec4b);
                        break;
                    case UniformVariableInfo.UniformType.FLOAT_MAT2:
                        Matrix2[] mat2 = new Matrix2[size];
                        for (int i = 0; i < size; i++)
                        {
                            mat2[i] = (Matrix2)uvValues[key][i].Mat;
                        }
                        SetVariableValue(program, key, mat2);
                        break;
                    case UniformVariableInfo.UniformType.FLOAT_MAT3:
                        Matrix3[] mat3 = new Matrix3[size];
                        for (int i = 0; i < size; i++)
                        {
                            mat3[i] = (Matrix3)uvValues[key][i].Mat;
                        }
                        SetVariableValue(program, key, mat3);
                        break;
                    case UniformVariableInfo.UniformType.FLOAT_MAT4:
                        Matrix4[] mat4 = new Matrix4[size];
                        for (int i = 0; i < size; i++)
                        {
                            mat4[i] = (Matrix4)uvValues[key][i].Mat;
                        }
                        SetVariableValue(program, key, mat4);
                        break;
                    case UniformVariableInfo.UniformType.SAMPLER_1D:
                        Texture1D[] tex1D = new Texture1D[size];
                        for (int i = 0; i < size; i++)
                        {
                            tex1D[i] = (Texture1D)uvValues[key][i].Sampler;
                        }
                        SetSamplerVariable(program, key, tex1D);
                        break;
                    case UniformVariableInfo.UniformType.SAMPLER_2D:
                        Texture2D[] tex2D = new Texture2D[size];
                        for (int i = 0; i < size; i++)
                        {
                            tex2D[i] = (Texture2D)uvValues[key][i].Sampler;
                        }
                        SetSamplerVariable(program, key, tex2D);
                        break;
                    case UniformVariableInfo.UniformType.SAMPLER_3D:
                        Texture3D[] tex3D = new Texture3D[size];
                        for (int i = 0; i < size; i++)
                        {
                            tex3D[i] = (Texture3D)uvValues[key][i].Sampler;
                        }
                        SetSamplerVariable(program, key, tex3D);
                        break;
                    //
                    // For now, ignore these samplers since we don't see any usage of these
                    //
                    case UniformVariableInfo.UniformType.SAMPLER_CUBE:
                        break;
                    case UniformVariableInfo.UniformType.SAMPLER_1D_SHADOW:
                        break;
                    default:
                        break;
                }
            }
    }

        #region Helper Methods
        //
        // Construct uniform variable associated with the shader program
        //
        //private static void UpdateUniformVariables(ShaderProgram program)
        //{
        //    Dictionary<string, UniformVariableInfo> uniformVariables = program.GetUniformVariables();
        //    uint programID = shaderPrograms[program];
        //    // clear all the old contents
        //    uniformVariables.Clear();
        //    string name = string.Empty;
        //    System.Text.StringBuilder variableName;
        //    int num, maxLength, size, length, type;
        //    int location = -1;
        //    // get the number of active uniform variables
        //    Gl.glGetProgramiv(programID, Tao.OpenGl.Gl.GL_ACTIVE_UNIFORMS, out num);
        //    // get the maximum length among active variables
        //    Gl.glGetProgramiv(programID, Tao.OpenGl.Gl.GL_ACTIVE_UNIFORM_MAX_LENGTH, out maxLength);
        //    variableName = new System.Text.StringBuilder(maxLength);
        //    // go through and update each uniform varible in UniformVariableInfo
        //    for (uint index = 0; index < num; index++)
        //    {
        //        // get length, size, type, and name; size indicates size of array 
        //        Gl.glGetActiveUniform(programID, index, maxLength, out length, out size, out type, variableName);
        //        name = System.Convert.ToString(variableName);
        //        location = Gl.glGetUniformLocation(programID, name);
        //        // ignore built-in variables (location for built-in variables = -1)
        //        if (location != -1)
        //        {
        //            UniformVariableInfo.UniformType uniformType = GetVariableType(type);
        //            UniformVariableInfo uniform = new UniformVariableInfo(size, uniformType, UniformVariableInfo.FoundInRoutine.DONT_CARE);
        //            uniformVariables.Add(name, uniform);
        //        }
        //    }
        //}

        //
        // Return Uniform Variable type 
        //
        private UniformVariableInfo.UniformType GetVariableType(int type)
        {
            UniformVariableInfo.UniformType _type;
            switch (type)
            {
                case Gl.GL_FLOAT:
                    _type = UniformVariableInfo.UniformType.FLOAT;
                    break;
                case Gl.GL_FLOAT_VEC2:
                    _type = UniformVariableInfo.UniformType.FLOAT_VEC2;
                    break;
                case Gl.GL_INT:
                    _type = UniformVariableInfo.UniformType.INT;
                    break;
                case Gl.GL_INT_VEC2:
                    _type = UniformVariableInfo.UniformType.INT_VEC2;
                    break;
                case Gl.GL_BOOL_VEC2:
                    _type = UniformVariableInfo.UniformType.BOOL_VEC2;
                    break;
                case Gl.GL_FLOAT_VEC3:
                    _type = UniformVariableInfo.UniformType.FLOAT_VEC3;
                    break;
                case Gl.GL_INT_VEC3:
                    _type = UniformVariableInfo.UniformType.INT_VEC3;
                    break;
                case Gl.GL_BOOL_VEC3:
                    _type = UniformVariableInfo.UniformType.BOOL_VEC3;
                    break;
                case Gl.GL_FLOAT_VEC4:
                    _type = UniformVariableInfo.UniformType.FLOAT_VEC4;
                    break;
                case Gl.GL_INT_VEC4:
                    _type = UniformVariableInfo.UniformType.INT_VEC4;
                    break;
                case Gl.GL_BOOL_VEC4:
                    _type = UniformVariableInfo.UniformType.BOOL_VEC4;
                    break;
                case Gl.GL_FLOAT_MAT2:
                    _type = UniformVariableInfo.UniformType.FLOAT_MAT2;
                    break;
                case Gl.GL_FLOAT_MAT3:
                    _type = UniformVariableInfo.UniformType.FLOAT_MAT3;
                    break;
                case Gl.GL_FLOAT_MAT4:
                    _type = UniformVariableInfo.UniformType.FLOAT_MAT4;
                    break;
                case Gl.GL_SAMPLER_1D:
                    _type = UniformVariableInfo.UniformType.SAMPLER_1D;
                    break;
                case Gl.GL_SAMPLER_2D:
                    _type = UniformVariableInfo.UniformType.SAMPLER_2D;
                    break;
                case Gl.GL_SAMPLER_3D:
                    _type = UniformVariableInfo.UniformType.SAMPLER_3D;
                    break;
                case Gl.GL_SAMPLER_CUBE:
                    _type = UniformVariableInfo.UniformType.SAMPLER_CUBE;
                    break;
                case Gl.GL_SAMPLER_1D_SHADOW:
                    _type = UniformVariableInfo.UniformType.SAMPLER_1D_SHADOW;
                    break;
                default:
                    _type = UniformVariableInfo.UniformType.SAMPLER_2D_SHADOW;
                    break;
            }
            return _type;

        }


        //
        // Return true if there is the same name as the given name found in the routine
        //
        private bool FoundInContent(string content, string name)
        {
            bool found = false;
            int searchComment = -1;
            int searchUniform = -1;
            int searchSampler = -1;
            int searchName = -1;
            string[] linesOfContent = content.Split('\n');
            string[] splitBySemicolon;
            for (int i = 0; i < linesOfContent.Length; i++)
            {
                linesOfContent[i] = linesOfContent[i].Trim();
                searchComment = linesOfContent[i].IndexOf("//");
                searchUniform = linesOfContent[i].IndexOf("uniform");
                // If there's a comment and it is ahead of the word, "uniform", ignore the line
                if (searchComment < 0 || searchComment >= searchUniform)
                {
                    splitBySemicolon = linesOfContent[i].TrimEnd().Split(';');
                    for (int j = 0; j < splitBySemicolon.Length; j++)
                    {
                        searchUniform = splitBySemicolon[j].IndexOf("uniform");
                        searchSampler = splitBySemicolon[j].IndexOf("sampler");
                        searchName = splitBySemicolon[j].IndexOf(name);
                        //
                        // Comparing each index is good enough in order to decide whether we have 
                        //  the name in the routine since the routine has been compiled succesfully (correct syntax)
                        // 
                        if (searchUniform < searchSampler && searchSampler < searchName)
                            found = true;
                    }
                }
            }
            return found;
        }
        #endregion
    }
}
